# 什么是设计模式

对软件设计开发过程中反复出现的某类问题的通用解决方案。是指导思想和方法论,不是特定的代码。

# 分类

  1. 结构型模式:通过识别系统中组件间的简单关系来简化系统的设计。
  2. 创建型模式:处理对象的创建,根据实际情况使用合适的方式创建对象。
  3. 行为型模式:用于识别对象之间常见的交互模式并加以实现。

# 一、结构型模式

# 外观模式

是什么:通过为子系统中的一组接口提供统一的高层接口,使子系统更容易使用。简而言之外观设计模式就是把多个子系统中复杂逻辑进行抽象,从而提供一个更统一、更简洁、更易用的API。

做什么:比如JQuery就把复杂的原生DOM操作进行了抽象和封装,并消除了浏览器之间的兼容问题,从而提供了一个更高级更易用的版本。

怎么做:以封装一个DOM事件绑定/解绑方法为例。

    function addEvent(element, event, handler) {
        if (element.addEventListener) {
            element.addEventListener(event, handler, false)
        } else if (element.attachEvent) {
            element.attachEvent('on' + event, handler)
        } else {
            element['on' + event] = handler
        }
    }

    function removeEvent(element, event, handler) {
        if (element.removeEventListener) {
            element.removeEventListener(event, handler, false)
        } else if (element.attachEvent) {
            element.detachEvent('on' + event, handler)
        } else {
            element['on' + event] = null
        }
    }

# 代理模式

是什么:想要访问某个对象,但是调用该对象的代价比较高(耗时、流程复杂等),可以封装一个代理对象,将对目标对象的调用封装在代理对象中,并对外暴露一个与目标对象相同(或更简单)的调用接口。

做什么:提高调用性能、简化调用复杂度

怎么做:以中介债券中数据合并为例,数据合并时,只关心债券代码和报价,如果合并过则缓存下来下次再有相同的代码和报价就直接返回。

    // 缓存代理
    function proxyMerge = (function() {
        const cache = {}    
        return function(newData) {
            const cacheData = cache[`${newData.code}_${newData.price}`]
            if (cacheData) {
                return cacheData
            }
            return merge(newData)   // merge函数需要遍历多个长数组,性能较差
        }
    })()

    // 使用
    console.log(proxyMerge(newData))    // 代理了:console.log(merge(newData))

# 二、创建型模式

# 工厂模式

是什么:可以看成是一个制造其他对象的对象,制造出的对象也会随着传入工厂对象参数的不同而有所区别。

做什么:提供一种集中化、统一化的方式创建对象,避免了分散创建对象导致的代码重复、灵活性差的问题。

怎么做:其实 jquery 的选择器就是工厂模式实现的,我们通过 $('div')、$('.div')、$('#div') 都可以创建一个jquery dom对象。

    class jQuery {
        constructor(selector) {

        }
        html() {

        }
        append() {
            
        }
    }
    window.$ = function(selector) {
        return new jQuery(selectpr)
    }

# 单例模式

是什么:顾名思义,单例模式中Class的实例个数最多为1。当需要一个对象去贯穿整个系统执行某些任务时,单例模式就派上了用场。而除此之外的场景尽量避免单例模式的使用,因为单例模式会引入全局状态,而一个健康的系统应该避免引入过多的全局状态。

做什么:全局提示弹窗(全局唯一)、唯一登录浮窗

怎么做:1. 隐藏Class的构造函数,避免多次实例化; 2. 通过暴露一个 getInstance() 方法来创建/获取唯一实例

    // 单例构造器
    const SingletonFoo = (function() {
        // 隐藏的 Class 构造函数
        function Foo() {
            // 一些业务代码
        }
        // 未初始化的单例对象
        let foo
        return {
            // 创建/获取单例对象
            getInstance: function() {
                if (!foo) {
                    foo = new Foo()
                }
                return foo
            }
        }
    })()

    // 使用
    const foo1 = SingletonFoo.getInstance()
    const foo2 = SingletonFoo.getInstance()

    console.log(foo1 === foo2)  // true

# 三、行为型模式

# 策略模式

是什么:定义一系列的算法,把它们一个个封装起来,可以方便扩展,并可以任意组合使用。

做什么:表单验证

怎么做:1. 封装不同策略的策略组;2. 通过不同组合来实现策略的 Context。

    // 表单验证为例
    // 封装策略组
    var strategies = {  
        isEmpty: function (value, errorMsg) {  
            if (value === '' || value === null) {  
                return errorMsg;  
            }  
        },  
        isMobile: function (value, errorMsg) { // 手机号码格式  
            if (!/(^1[3|4|5|7|8][0-9]{9}$)/.test(value)) {  
                return errorMsg;  
            }  
        },  
        minLength: function (value, length, errorMsg) {  
            if (value.length < length) {  
                return errorMsg;  
            }  
        }  
    };

    // 验证 “手机号” 的 Context
    function validMobile(dom) {
        let value = dom.value
        let isEmpty = strategies.isEmpty(value, '手机号不能为空')
        let isValid = strategies.isMobile(value,'手机号格式错误')
        let errorMsg = isEmpty||isValid
        if(errorMsg){  
            alert(errorMsg); 
            return false;  
        } 
    }

# 观察者模式

是什么:对象间的一种一对多的依赖关系;当被观察者的状态发生改变时,其他观察者皆会得到通知并自动更新。

做什么

怎么做

    // 被观察者 Subject
    class Subject {
        constructor() {
            this.state = 1
            this.observers = []     // 观察者列表
        }

        setState(state) {
            this.state = state  
            this.notify()
        }

        add(observer) {
            this.observers.push(observer)
        }

        notify() {
            this.observers.forEach(observer => {
                observer.update()
            })
        }
    }

    // 观察者 Observer
    class Observer {
        constructor(name, subject) {
            this.name = name
            this.subject = subject
            this.subject.add(this)      // 将自己加入观察者列表中
        }

        update() {
            console.log(`now, subject state is ${this.subject.state}, so ${this.name} has updated`)
        }
    }

    const sub = new Subject()
    const o1 = new Observer('observer1', sub)
    const o2 = new Observer('observer2', sub)
    const o3 = new Observer('observer3', sub)

    sub.setState(2)

# 发布订阅模式

是什么:发布者发布消息时不会直接把消息发给特定的接收者,而是需要一个中间消息管理器来发送到特定的接收者。所以与观察者模式的区别是:1. 观察者模式只有两个角色 被观察者(发布者) 和 观察者(订阅者),而发布订阅模式还有一个“中间消息管理器”;2. 观察者模式两个角色间是松耦合,而发布订阅模式两个角色间完全解耦。

做什么:“事件驱动”项目中,实现组件间通信

怎么做

    class EventBus {        // 中间消息管理器
        constructor() {
            this.events = {}
        }

        on(evt, handler) {  // 订阅
            this.events[evt] = this.events[evt] || []
            this.events[evt].push({

            })
        }

        emit(evt, args) {   // 发布
            if (!this.events[evt]) return
            for (let i = 0; i < events[evt].length; i++) {
                this.events[evt][i]()
            }
        }

        off(evt) {          // 取消订阅
            delete this.events[evt]
        }
    }

    // 使用
    const bus = new EventBus()

    // 订阅
    bus.on('evt1', function(val) {
        console.log('emit evt1, val is' + val)
    })
    bus.on('evt2', function(val) {
        console.log('emit evt2, val is' + val)
    })

    // 发布
    bus.emit('evt1', '1')
    bus.emit('evt2', '2')
最后更新时间: 3/28/2021, 9:04:52 PM